/**************************************************************************\
*//*! \file nanotube_pcap_read.cpp
** \author  Stephan Diestelhorst <stephand@amd.com>
**          Neil Turton <neilt@amd.com>
**  \brief  Nanotube pcap reading.
**   \date  2020-08-26
*//*
\**************************************************************************/

/**************************************************************************
** Copyright (C) 2023, Advanced Micro Devices, Inc. All rights reserved.
** SPDX-License-Identifier: MIT
**************************************************************************/

#include "nanotube_pcap_read.hpp"

#include "nanotube_packet.hpp"

#include <assert.h>
#include <cstring>
#include <endian.h>

nanotube_pcap_read::nanotube_pcap_read(const char *file)
{
  char errbuf[PCAP_ERRBUF_SIZE];

  pcap = pcap_open_offline(file, errbuf);
  if (pcap == nullptr) {
    std::cerr << "pcap read: Error opening packet container: " << errbuf
              << '\n';
    exit(1);
  }
}

nanotube_pcap_read::~nanotube_pcap_read()
{
  pcap_close(pcap);
}

nanotube_packet_ptr_t nanotube_pcap_read::read_next(void)
{
  struct pcap_pkthdr hdr;
  const uint8_t *pcap_data = pcap_next(pcap, &hdr);
  if( pcap_data == nullptr)
    return nanotube_packet_ptr_t();

  nanotube_packet_ptr_t p(new nanotube_packet_t);
  p->set_metadata_specified(false);
  if (pcap_datalink(pcap) == DLT_EN10MB) { // Raw ethernet frames
    p->insert(NANOTUBE_SECTION_PAYLOAD, pcap_data, 0, hdr.caplen);
  }
  else if (pcap_datalink(pcap) == DLT_PPI) { // PPI-encapsulated frames
    // Ensure the 12-byte PPI header is 32-bit aligned so the loads below are valid.
    uint32_t ppi_hdr[3];
    uint8_t* ppi_data = (uint8_t*)ppi_hdr;
    memcpy(ppi_data, pcap_data, 12);
    assert((((uintptr_t)ppi_data) & 0x3) == 0);
    // See header format at the end of this file.
    assert(ppi_data[0] == 0); // pph_version, must be 0
    assert(ppi_data[1] == 0); // pph_flags, we don't support alignment=1
    uint16_t pph_len = le16toh(*(uint16_t*)&ppi_data[2]);
    uint32_t pph_dlt = le32toh(*(uint32_t*)&ppi_data[4]);
    assert(pph_dlt == DLT_EN10MB); // want ethernet packets only
    assert(pph_len >= 8);
    if (pph_len > 8) {
      // Now parse 4-byte field header
      assert(pph_len >= 12);
      uint16_t pfh_type = le16toh(*(uint16_t*)&ppi_data[8]);
      uint16_t pfh_datalen = le16toh(*(uint16_t*)&ppi_data[10]); // metadata length
      const uint8_t* md_ptr = &pcap_data[12];
      // FIXME: we should have a proper metadata type identifier. Use this
      // bodged-up value for now (as generated by scripts/p4_slice_test_to_nt).
      assert(pfh_type == 0xF00D);
      // FIXME: the spec supports more than one field header, but we don't for now.
      assert(pph_len == pfh_datalen + 12); // 12 = 8B pph + 4B pfh
      p->insert(NANOTUBE_SECTION_METADATA, md_ptr, 0, pfh_datalen);
      p->set_metadata_specified(true);
    }
    p->insert(NANOTUBE_SECTION_PAYLOAD, &pcap_data[pph_len], 0, hdr.caplen - pph_len);
  }

  return p;
}
/*
 * PPI headers spec: https://web.archive.org/web/20160328114748/http://www.cacetech.com/documents/PPI%20Header%20format%201.0.7.pdf
 *
 * Rest of this comment from: https://github.com/boundary/wireshark/blob/master/epan/dissectors/packet-ppi.c
 *
 * PPI headers have the following format:
 *
 * ,---------------------------------------------------------.
 * | PPH | PFH 1 | Field data 1 | PFH 2 | Field data 2 | ... |
 * `---------------------------------------------------------'
 *
 * The PPH struct has the following format:
 *
 * typedef struct ppi_packetheader {
 *     guint8  pph_version;     // Version.  Currently 0
 *     guint8  pph_flags;       // Flags.
 *     guint16 pph_len; // Length of entire message, including this header and TLV payload.
 *     guint32 pph_dlt; // libpcap Data Link Type of the captured packet data.
 * } ppi_packetheader_t;
 *
 * The PFH struct has the following format:
 *
 * typedef struct ppi_fieldheader {
 *     guint16 pfh_type;        // Type
 *     guint16 pfh_datalen;     // Length of data
 * } ppi_fieldheader_t;
 */
